﻿
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Text;
using LFSRecord2.Misc;
using System;
using System.Reflection;
using InSimDotNet.Packets;
namespace LFSRecord2.Model.Replay
{
    public class ReplayData
    {
        ushort _saveFileVersion = 1;

        /// <summary>
        /// Name of the replay that this data was recorded from. Only the file name. Path is not included.
        /// </summary>
        public string ReplayName { get; set; }

        /// <summary>
        /// The type of replay
        /// </summary>
        public ReplayTypes ReplayType
        {
            get
            {
                if (ReplayName.Length < 3)
                    return ReplayTypes.NoReplay;

                switch (ReplayName.Substring(ReplayName.Length - 3).ToUpperInvariant())
                {
                    case "SPR":
                        return ReplayTypes.Spr;
                    case "MPR":
                        return ReplayTypes.Mpr;
                }
                return ReplayTypes.NoReplay;
            }
        }

        /// <summary>
        /// The total length of the replay (as reported by LFS), in seconds
        /// </summary>
        public double ReplayLength { get; set; }

        /// <summary>
        /// The time into the replay where data recording started, in seconds
        /// </summary>
        public double RecordStartTime { get; set; }

        /// <summary>
        /// The time into the replay where data recording ended, in seconds
        /// </summary>
        public double RecordEndTime { get; set; }

        /// <summary>
        /// The replay time we are currently at, when recording, in seconds
        /// </summary>
        public double CurRecordTime { get; set; }

        /// <summary>
        /// Collection of recorded player data
        /// </summary>
        public Collection<ReplayPlayerData> Players { get; set; }

        public ReplayData() 
            : this("")
        {
        }

        public ReplayData(string replayName)
        {
            ReplayName = replayName;
            Players = new Collection<ReplayPlayerData>();
        }

        public ReplayPlayerData GetPlayerByID(int playerID)
        {
            foreach (ReplayPlayerData player in Players)
            {
                if (player.PlayerID == playerID)
                    return player;
            }

            return null;
        }

        public ReplayPlayerData GetPlayerByIndex(int playerIndex)
        {
            return (playerIndex >= Players.Count) ? null : Players[playerIndex];
        }

        public ReplayPlayerData GetPlayerByName(string playerName)
        {
            foreach (ReplayPlayerData player in Players)
            {
                if (player.PlayerName == playerName)
                    return player;
            }

            return null;
        }

        public int GetPlayerIndexByName(string playerName)
        {
            for (int a = 0; a < Players.Count; a++)
            {
                if (Players[a].PlayerName == playerName)
                    return a;
            }

            return -1;
        }

        public void PostProcess()
        {
            double t;
            double mciInterval = (LfsRecordController.LfsState.InSimVersion <= 4) ? 0.05 : 0.04;
            Debug.WriteLine("postprocessing with mciInterval {0}", mciInterval);

            foreach (ReplayPlayerData player in Players)
            {
                if (player.DataNodes.Count == 0)
                    continue;

                t = player.DataNodes[0].ReplayTime;
                for (int a = 1; a < player.DataNodes.Count; a++)
                {
                    // Increase time upon every mci packet
                    if (player.DataNodes[a].MciInfo != null)
                        t += mciInterval;

                    player.DataNodes[a].ReplayTime = t;
                }
            }
        }

        /// <summary>
        /// Store ourselves to file.
        /// </summary>
        public void Save(string filePath)
        {
            Debug.WriteLine("Saving replay data : '" + filePath + "'");

            using (FileStream fs = File.OpenWrite(filePath))
            using (BinaryWriter bfs = new BinaryWriter(fs, Encoding.ASCII))
            {
                Save(filePath, fs, bfs);
            }
        }

        public void Save(string filePath, FileStream fs, BinaryWriter bfs)
        {
            bfs.Write(FileHelper.StringToByteArray("LFSRD\0"));
            bfs.Write(_saveFileVersion);

            bfs.Write(FileHelper.StringToByteArray(ReplayName.PadRight(64, Char.MinValue)));

            bfs.Write(ReplayLength);
            bfs.Write(RecordStartTime);
            bfs.Write(RecordEndTime);

            bfs.Write(Players.Count);

            // Write player blocks
            Type t;
            object val;
            Encoding encoding = new UTF8Encoding();
            byte[] stringBytes;
            int a;
            foreach (ReplayPlayerData rpd in Players)
            {
                bfs.Write(rpd.PlayerID);

                stringBytes = encoding.GetBytes(rpd.PlayerName);
                for (a = 0; a < stringBytes.Length; a++)
                {
                    if (a == 24)
                        break;
                    bfs.Write(stringBytes[a]);
                }
                a = stringBytes.Length;
                while (a < 24)
                {
                    bfs.Write(Char.MinValue);
                    a++;
                }
                Debug.WriteLine("Byte Length of " + rpd.PlayerName + " stringBytes : {0}", stringBytes.Length);
                //bfs.Write(FileHelper.StringToByteArray(rpd.PlayerName.PadRight(24, Char.MinValue)));
                
                bfs.Write(rpd.DataNodes.Count);

                foreach (ReplayDataNode rNode in rpd.DataNodes)
                {
                    bfs.Write(rNode.ReplayTime);

                    if (rNode.IsMciNode)
                    {
                        // Write the CompCar type
                        bfs.Write(UInt32.MaxValue);

                        // Write CompCar contents
                        t = rNode.MciInfo.GetType();
                        foreach (PropertyInfo p in t.GetProperties())
                        {
                            val = p.GetValue(rNode.MciInfo, null);

                            // Write the value.
                            if (val is int)
                            {
                                bfs.Write((int)val);
                            }
                            else if (val is double)
                            {
                                bfs.Write((double)val);
                            }
                            else if (val is ushort)
                            {
                                bfs.Write((ushort)val);
                            }
                            else if (val is short)
                            {
                                bfs.Write((short)val);
                            }
                            else if (val is CompCarFlags)
                            {
                                bfs.Write((int)val);
                            }
                            else
                            {
                                Debug.WriteLine("Unknown data type ({0}) {1} = {2}", p.GetType().ToString(), p.Name, p.GetValue(rNode.MciInfo, null));
                            }
                        }
                    }
                    else
                    {
                        // Write the PacketType
                        bfs.Write((uint)rNode.Packet.Type);

                        // Regular insim packets - not implemented atm.
                        t = rNode.Packet.GetType();
                        foreach (PropertyInfo p in t.GetProperties())
                        {
                            if (p.Name == "Size" || p.Name == "ReqI" || p.Name == "Type")
                                continue;

                            val = p.GetValue(rNode.MciInfo, null);
                        }
                    }
                }
            }
        }

        public void Load(string filePath)
        {
            Debug.WriteLine("Loading replay data : '" + filePath + "'");

            try
            {
                Players.Clear();

                using (FileStream fs = File.OpenRead(filePath))
                using (BinaryReader bfs = new BinaryReader(fs, Encoding.ASCII))
                {
                    Load(filePath, fs, bfs);
                }
            }
            catch
            {
                Players.Clear();
                throw;
            }
        }

        public void Load(string filePath, FileStream fs, BinaryReader bfs)
        {
            // Verify that we're (probably) going to load actual LFSRecord replay data (and not an old version either)
            if (fs.Length < 100 || FileHelper.CharArrayToString(bfs.ReadChars(6)) != "LFSRD")
            {
                throw new ReplayDataLoadException("Error loading replay data :\n\nFile does not appear to be a LFSRecord Replay Data file");
            }

            if (bfs.ReadUInt16() != _saveFileVersion)
            {
                throw new ReplayDataLoadException("Error loading replay data :\n\nOutdated file version");
            }

            ReplayName = FileHelper.CharArrayToString(bfs.ReadChars(64));

            ReplayLength = bfs.ReadDouble();
            RecordStartTime = bfs.ReadDouble();
            RecordEndTime = bfs.ReadDouble();

            int numPlayers = bfs.ReadInt32();
            if (numPlayers > 255)
            {
                throw new ReplayDataLoadException("Error loading replay data :\n\nImpossible number of players detected.");
            }

            // Player blocks
            ReplayPlayerData rpd;
            int numNodes;
            ReplayDataNode rNode;
            CompCarCustom ccc;
            uint pType;
            PacketType packetType;
            Type t;
            object val;
            for (int a = 0; a < numPlayers; a++)
            {
                rpd = new ReplayPlayerData(bfs.ReadInt32(), FileHelper.ByteArrayToUtf8(bfs.ReadBytes(24)));
                numNodes = bfs.ReadInt32();

                for (int b = 0; b < numNodes; b++)
                {
                    rNode = new ReplayDataNode();
                    rNode.ReplayTime = bfs.ReadDouble();

                    pType = bfs.ReadUInt32();
                    if (pType == UInt32.MaxValue)
                    {
                        // CompCar data
                        ccc = new CompCarCustom();
                        t = ccc.GetType();
                        foreach (PropertyInfo p in t.GetProperties())
                        {
                            val = p.GetValue(ccc, null);

                            if (val is int)
                            {
                                p.SetValue(ccc, bfs.ReadInt32(), null);
                                //Debug.WriteLine("Read int32 value : {0} = {1}", p.Name, p.GetValue(ccc, null));
                            }
                            else if (val is double)
                            {
                                p.SetValue(ccc, bfs.ReadDouble(), null);
                                //Debug.WriteLine("Read double value : {0} = {1}", p.Name, p.GetValue(ccc, null));
                            }
                            else if (val is ushort)
                            {
                                p.SetValue(ccc, bfs.ReadUInt16(), null);
                                //Debug.WriteLine("Read uint16 value : {0} = {1}", p.Name, p.GetValue(ccc, null));
                            }
                            else if (val is short)
                            {
                                p.SetValue(ccc, bfs.ReadInt16(), null);
                                //Debug.WriteLine("Read int16 value : {0} = {1}", p.Name, p.GetValue(ccc, null));
                            }
                            else if (val is CompCarFlags)
                            {
                                p.SetValue(ccc, bfs.ReadInt32(), null);
                                //Debug.WriteLine("Read value : {0} = {1}", p.Name, p.GetValue(ccc, null));
                            }
                            else
                            {
                                Debug.WriteLine("Unknown data type : {0}", p.Name);
                            }
                        }
                        rNode.MciInfo = ccc;
                    }
                    else
                    {
                        // Regular packet data - check if valid packet identifier
                        try
                        {
                            packetType = (PacketType)Enum.ToObject(typeof(PacketType), pType);
                        }
                        catch (Exception ex)
                        {
                            throw new ReplayDataLoadException("Error loading replay data :\n\nInvalid packet type detected", ex);
                        }

                        // Packet reading not yet implemented (because saving isn't either)

                    }

                    rpd.DataNodes.Add(rNode);
                }

                Players.Add(rpd);
            }
        }
    }
}
